Sum factorisation on simplices - #5263
Conversation
Extends tsfc/fem.py's translate_coefficient and translate_argument to
take a sum-factorized (Duffy/lattice) tabulation path for simplicial DG
Legendre elements under dx(scheme="collapsed"), targeting O(p^{d+1})
flops for the matrix-free residual instead of the dense O(p^{2d}).
The lattice multiindex from Legendre.duffy_evaluation is gathered
against coefficients and scattered back to the flat dof index through
FIAT's existing Morton dof numbering (new morton_forward_table /
morton_inverse_table in FIAT.expansions), entirely inside fem.py:
element.index_shape and argument_multiindices stay flat, so no
driver.py or kernel_interface changes are needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pbrubeck
left a comment
There was a problem hiding this comment.
TSFC codegen diff should be as tight as possible. Add the new code in a separate finat submodule.
…rred Legendre's dof order is now lattice-lexicographic (permuted in FIAT), which simplified finat/duffy.py's index arithmetic but did not eliminate the VariableIndex gather/scatter: get_indices() must stay a flat index because it can't distinguish a cell-interior kernel from a facet-integral kernel, and facet tabulation always uses the dense, flat-(ndof,) FIAT path. Fully eliminating the gather/scatter would need a bespoke jagged gem.FlexiblyIndexed view in kernel_interface/ common.py's prepare_arguments/prepare_coefficient, shared code every Firedrake kernel depends on -- deliberately deferred as a separate, higher-risk follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Temporarily pins firedrake-fiat to pbrubeck/simplex-sum-factor so CI exercises this branch's paired FIAT changes (dof-order permutation, gem.Delta fix). Revert to @main once the FIAT PR merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
translate_coefficient no longer dispatches on isinstance(element, DuffyElement): finat.duffy.DuffyElement.basis_evaluation now returns an already flat-dof-indexed tabulation, so the generic dense contraction path applies uniformly and recovers the same sum-factorized complexity without any special-casing. This also makes the Duffy fast path compose with Vector/TensorElement, which previously fell through to the slow path since TensorFiniteElement is never itself a DuffyElement instance. test_duffy_scatter_and_contract updated to contract generically instead of calling the now-removed duffy_contraction. test_collapsed_quadrature_sum_factorisation now compares against dx(scheme="canonical") instead of the default scheme: same collapsed Gauss-Jacobi points/weights as dx(scheme="collapsed"), but tabulated via the dense FIAT path, isolating the comparison to the sum-factorized tabulation itself rather than to a difference in quadrature rule. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rckirby
left a comment
There was a problem hiding this comment.
This just checks that the code gives a correct answer. Do we have a way of checking whether the algorithm has the right complexity?
This PR adds both correctness and complexity tests. Complexity in flops is not enough, I also had to enforce tests on temporaries |
I see those tests, was thinking about FIAT. We should also test out Bernstein in the one-element benchmarks -- it doesn't have the indirection internally that modified C^0 expansions have but can be directly (after Duffy) sum-factored. |
7cfae19 to
6e30e23
Compare
Depends on firedrakeproject/fiat#262.
Summary
gem.JaggedIndexrepresents simplex lattice indices with bounds depending on parent indices. TSFC lowers these to parameterized Loopy domains.gem.FlattenedTensorexposes a jagged lattice through a flat DoF index while retaining its tensor-product structure.gem.sparse_matrixrepresents sparse basis recombination.DuffyElement.duffy_evaluation()constructs sum-factorized Legendre and IntegratedLegendre tabulations;get_sparse_coeffs()provides DG ordering or CG recombination data.gem.optimisenow:unflatten()andunflatten_returns();FlattenedTensors to gather tables withreplace_flattened().For a three-dimensional collapsed basis, the reference derivative has
3 + 2 + 1one-dimensional derivative paths. A bilinear Laplacian combines two such arguments and three physical gradient components. The previousunflatten_returns()expanded the first argument before exposing the second, cloning the second jagged lattice once per derivative term. The new ordering exposes both lattices jointly, then groups equal monomials and factorizes one contraction index at a time.The final output loops remain genuinely jagged. In particular, the innermost tetrahedral domain has a bound of the form
0 <= k <= p - i - j; it is not replaced by a rectangular loop with masking.Bilinear Laplacian code-generation benchmarks
These are tetrahedral Bernstein kernels generated in spectral mode. Structural counters and FLOPs are exact for the generated Loopy domains; times are representative local instrumented compiler runs.
unflatten_returnsforloopsDegree 10 is the tested crossover where the sum-factorized bilinear Laplacian beats canonical quadrature in both generated FLOPs and compiler time. A regression test also checks the compact instruction/temporary/loop counts and requires a two-parent parameterized domain, which preserves the tetrahedral
JaggedIndexloop.One-element execution benchmarks
FLOPs use the exact generated jagged loop domains. Times are median assembly times after warm-up.
AI assistance: OpenAI Codex was used for code analysis, implementation support, testing, and benchmarking.